package com.starsoft.quant.strategy.etf;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.starsoft.frame.util.DateUtil;
import com.starsoft.frame.util.StringUtil;
import com.starsoft.quant.bean.QuantDetail;
import com.starsoft.quant.executor.StrategyReport;
import com.starsoft.quant.strategy.StrategyImpl;
import com.starsoft.smdc.bean.SmdcDiv;
import com.starsoft.smdc.bean.SmdcHandicap;
import com.starsoft.smdc.bean.SmdcMarketDaily;
import com.starsoft.smdc.bean.SmdcSecurity;
import com.starsoft.smdc.util.SecurityType;
import com.starsoft.trade.bean.TradeAcountStatus;
import com.starsoft.trade.bean.TradeBill;
import com.starsoft.trade.bean.TradePosition;
import com.starsoft.trade.order.Order;
import com.starsoft.trade.util.TradeType;

public class EtfStragegy extends StrategyImpl {
	private static final Logger logger = LoggerFactory.getLogger(EtfStragegy.class);
	
	private Map<String, SmdcMarketDaily> marketMap;
	private Map<String, SmdcMarketDaily> lastDayMarketMap;
	private Map<String, SmdcDiv> divMap;
	
	private double YU_ZHI;
	private boolean IS_GROUP_INDEX;
	@SuppressWarnings("rawtypes")
	private Map ETF_CONFIG;
	private Date changePosDate;

	
	@Override
	public StrategyReport handle() {
		YU_ZHI = Double.parseDouble(modelContext.getModelParam("yuzhi"));
		IS_GROUP_INDEX = Boolean.parseBoolean(modelContext.getModelParam("isGroupIndex"));
		ETF_CONFIG = initFundPrem();
		
		changePosDate = getChangePosDate();
		
		List<SmdcSecurity> securityList = context.getSecurityList();
		loadSmdcData(securityList);

		List<QuantDetail> details = new ArrayList<>();
		Map<String, List<QuantDetail>> indexDetailMap = new HashMap<>();
		for (SmdcSecurity security : securityList) {
			try {
				QuantDetail report = analysis(security);
				if (report != null) {
					details.add(report);
					
					String baseIndex = security.getFundBaseindex();
					List<QuantDetail> detailsIndex = indexDetailMap.get(baseIndex);
					if(detailsIndex==null){
						detailsIndex = new ArrayList<>();
						indexDetailMap.put(baseIndex, detailsIndex);
					}
					detailsIndex.add(report);
				}
			} catch (Exception e) {
				logger.error(security.getSecId() + " " + DateUtil.toString(context.getCurrentDate()), e);
				context.getRunLog().append(security.getSecId()).append(" ");
				context.getRunLog().append(security.getSecName()).append(" ");
				context.getRunLog().append(DateUtil.toString(context.getCurrentDate())).append(" ");
				context.getRunLog().append(e.getMessage() + " ").append(e.getClass().toString()).append("\n");
			}
		}
		
		StrategyReport report = createReport(details);
		List<Order> orders = new ArrayList<>();
		if(IS_GROUP_INDEX){
			for(List<QuantDetail> detailsIndex : indexDetailMap.values()){
				orders.addAll(order(detailsIndex));
			}
		} else {
			orders.addAll(order(details));
		}
		report.setOrders(orders);
		return report;
	}
	
	private List<Order> order(List<QuantDetail> details) {
		List<Order> orders = new ArrayList<>();
		
		Integer acountId = context.getModelContext().getAcountId();
		TradeAcountStatus status = context.getAcountStatus();
		List<TradePosition> positions = status.getPositions();

		QuantDetail detailCheap = getCheap(details);
		double cheapPrime = Double.parseDouble(detailCheap.getCol6());
		Double leftCash = status.getSummary().getCash();
		// sell
		for (TradePosition position : positions) {
			String secId = position.getId().getSecId();
			QuantDetail detail = null;
			for(QuantDetail detailA : details){
				if(secId.equals(detailA.getId().getSecId())){
					detail = detailA;
					break;
				}
			}
			if (detail == null) {
				continue;
			}
			if (cheapPrime - detail.getRating() > YU_ZHI && position.getSecNumAfs() > 0) {
				double num = position.getSecNumAfs();
				double closePrice = getOrderPrice(marketMap.get(secId), true);
				Order order = new Order(secId, -num, closePrice);
				leftCash += context.getOrderService().getOrderMoney(acountId, order);
				orders.add(order);
			}
		}
		// buy
		if (leftCash > 500) {
			String secId = detailCheap.getId().getSecId();
			double closePrice = getOrderPrice(marketMap.get(secId), false);
			double num = (int) Math.floor(leftCash / closePrice / 100) * 100;
			Order order = new Order(secId, num, closePrice);
			leftCash += context.getOrderService().getOrderMoney(acountId, order);
			if (leftCash < 0) {
				num = num - 100;
				order.setSecNum(num);
			}
			orders.add(order);
		}
		return orders;
	}

	private QuantDetail getCheap(List<QuantDetail> details){
		QuantDetail cheapOne = null;
		double cheapDiscount = 0;
		for (QuantDetail detail : details) {
			if (cheapOne == null) {
				cheapOne = detail;
				cheapDiscount = Double.parseDouble(detail.getCol6());
			} else{
				double discount = Double.parseDouble(detail.getCol6());
				if(discount>cheapDiscount){
					cheapDiscount = discount;
					cheapOne = detail;
				}
			}
		}
		return cheapOne;
	}
	
	private void loadSmdcData(List<SmdcSecurity> securityList){
		marketMap = context.getMarketDailyMap();
		Map<String, SmdcSecurity> indexMap = new HashMap<>();
		for (SmdcSecurity security : securityList) {
			boolean isFund = SecurityType.isFund(security.getSecType());
			boolean isIndexFund = security.getFundIsindex()!=null && security.getFundIsindex();
			String baseIndexId = security.getFundBaseindex();
			SmdcSecurity baseIndex = null;
			if(!StringUtil.isEmpty(baseIndexId)){
				baseIndex = context.getSecurityService().get(baseIndexId);
			}
			if (isFund && isIndexFund && baseIndex != null) {
				if(!indexMap.containsKey(baseIndexId)){
					indexMap.put(baseIndexId, baseIndex);
				}
			} else {
				throw new RuntimeException(security.getSecId()+"不满足策略条件，指数型基金");
			}
		}
		
		Date lastDay = context.getHolidayService().getNextBusinessDay(context.getCurrentDate(), -1);
		List<String> secIds = new ArrayList<>(indexMap.keySet());
		for (SmdcSecurity security : securityList) {
			secIds.add(security.getSecId());
		}
		lastDayMarketMap = context.getMarketDailyService().getMarketMap(secIds, lastDay);
		
		if (context.isToday()) {
			List<SmdcSecurity> secHandicapList = new ArrayList<>(securityList);
			secHandicapList.addAll(indexMap.values());
			List<SmdcHandicap> handicapList = context.getHandicapService().getHandicap(secHandicapList, false);
			handicapList.forEach(p -> {
				SmdcMarketDaily newMarket = p.toMarket();
				SmdcMarketDaily oldMarket = marketMap.get(p.getSecId());
				if (oldMarket != null) {
					newMarket.setAdjFactor(oldMarket.getAdjFactor());
					newMarket.setShareFactor(oldMarket.getShareFactor());
				}
				marketMap.put(p.getSecId(), newMarket);
			});
		} else {
			secIds = new ArrayList<>(indexMap.keySet());
			marketMap.putAll(context.getMarketDailyService().getMarketMap(secIds, context.getCurrentDate()));
		}

		divMap = new HashMap<>();
		for (SmdcSecurity security : securityList) {
			SmdcDiv div = context.getDivService().get(security.getSecId(), context.getCurrentDate());
			if(div!=null){
				divMap.put(security.getSecId(), div);
			}
		}
	}

	private QuantDetail analysis(SmdcSecurity security) {
		String secId = security.getSecId();
		List<TradePosition> posnList = context.getAcountStatus().getPositions();
		boolean hasPos = false;
		for (TradePosition position : posnList) {
			if (position.getId().getSecId().equals(secId) && position.getSecNum() != 0) {
				hasPos = true;
				break;
			}
		}
		SmdcMarketDaily market = marketMap.get(secId);
		Double nav = getPredictNav(security);
		Double sell = market.getSell1();
		Double sellVol = market.getSellVol1();
		Double sellDiscount = null;
		if (sell == null || sell == 0) {
			sellDiscount = (1 - (market.getClosePrice() + 0.001) / nav) * 100;
		} else {
			sellDiscount = (1 - sell / nav) * 100;
		}
		sellDiscount = sellDiscount-getEtfConfigSell(secId);
		Double buy = market.getBuy1();
		Double buyVol = market.getBuyVol1();
		Double buyDiscount = null;
		if (buy == null || buy == 0) {
			buyDiscount = (1 - (market.getClosePrice() - 0.001) / nav) * 100;
		} else {
			buyDiscount = (1 - buy / nav) * 100;
		}
		buyDiscount = buyDiscount-getEtfConfigBuy(secId);
		Double rating = hasPos ? buyDiscount : sellDiscount;
		
		Double lastNav = lastDayMarketMap.get(secId).getSecNav();
		String baseIndexId = security.getFundBaseindex();
		String baseIndexName = context.getSecurityService().get(baseIndexId).getSecName();
		Double baseIndexChange = marketMap.get(baseIndexId).getPercent() * 100;
		Double lastPredictDiff = getLastPredicDiff(security);
		
		Double changePosProfit = null;
		if(!DateUtil.isToday(changePosDate)){
			SmdcMarketDaily changePosDateMarket = context.getMarketDailyService().getMarketDaily(security, changePosDate);
			if(changePosDateMarket!=null){
				changePosProfit = market.getClosePrice()* market.getAdjFactor()/changePosDateMarket.getClosePrice()/changePosDateMarket.getAdjFactor();
				changePosProfit = (changePosProfit-1)*100;
			}
		}else {
			changePosProfit = 0.0;
		}
		
		QuantDetail report = createDetail(security, rating);
		report.setCol1(StringUtil.toStrNull(nav));
		report.setCol2(StringUtil.toStrNull(buy));
		report.setCol3(StringUtil.toStrNull(buyDiscount));
		report.setCol4(StringUtil.toStrNull(buyVol));
		report.setCol5(StringUtil.toStrNull(sell));
		report.setCol6(StringUtil.toStrNull(sellDiscount));
		report.setCol7(StringUtil.toStrNull(sellVol));
		report.setCol8(StringUtil.toStrNull(lastNav));
		report.setCol9(StringUtil.toStrNull(baseIndexName));
		report.setCol10(StringUtil.toStrNull(baseIndexChange));
		report.setCol11(StringUtil.toStrNull(lastPredictDiff));
		report.setCol12(StringUtil.toStrNull(market.getTurnoverValue()));
		report.setCol13(StringUtil.toStrNull(security.getFundManageFee()));
		report.setCol14(StringUtil.toStrNull(changePosProfit));
		report.setCol15(DateUtil.toString(changePosDate));
		report.setCol16(StringUtil.toStrNull(buyDiscount+getEtfConfigBuy(secId)));
		report.setCol17(StringUtil.toStrNull(sellDiscount+getEtfConfigSell(secId)));
		return report;
	}
	
	private double getPredictNav(SmdcSecurity security) {
		String secId = security.getSecId();
		double lastNav = lastDayMarketMap.get(secId).getSecNav();
		String baseIndexId = security.getFundBaseindex();
		double baseIndexChange = marketMap.get(baseIndexId).getPercent();
		SmdcDiv div = divMap.get(security.getSecId());
		if (div != null) {
			lastNav = lastNav - div.getPerCashDiv();
		}
		return lastNav * (1 + baseIndexChange);
	}
	
	private Double getLastPredicDiff(SmdcSecurity security) {
		String secId = security.getSecId();
		double lastNav = lastDayMarketMap.get(secId).getSecNav();
		if (context.getLastReport() != null) {
			QuantDetail lastDetail = context.getLastReport().getDetail(secId);
			if (lastDetail != null) {
				Double lastPredict = Double.parseDouble(lastDetail.getCol1());
				double predictDiff = (lastPredict - lastNav) / lastNav * 10000;
				return predictDiff;
			}
		}
		return null;
	}
	
	private double getOrderPrice(SmdcMarketDaily market, boolean isSell) {
		double result = 0;
		if (isSell) {
			if (market.getBuy1() != null) {
				result = market.getBuy1();
			} else {
				result = market.getClosePrice();
			}
		} else {
			if (market.getSell1() != null) {
				result = market.getSell1();
			} else {
				result = market.getClosePrice();
			}
		}
		return result;
	}

	@SuppressWarnings({"rawtypes" })
	private Map initFundPrem(){
		String fundPremStr = modelContext.getModelParam("etfConfig");
		if(!StringUtil.isEmpty(fundPremStr)){
			ObjectMapper om = new ObjectMapper();
			try {
				return om.readValue(fundPremStr, HashMap.class);
			} catch (IOException e) {
				context.getRunLog().append("etfConfig 配置错误\n");
			}
		}
		return new HashMap();
	}
	
	@SuppressWarnings("rawtypes")
	private double getEtfConfigBuy(String secId) {
		Map config = (Map) ETF_CONFIG.get(secId);
		if (config == null) {
			return 0;
		} else {
			return (double) config.get("buy");
		}
	}

	@SuppressWarnings("rawtypes")
	private double getEtfConfigSell(String secId) {
		Map config = (Map) ETF_CONFIG.get(secId);
		if (config == null) {
			return 0;
		} else {
			return (double) config.get("sell");
		}
	}
	
	private Date getChangePosDate(){
		Integer acountId = context.getModelContext().getAcountId();
		Date startDate = context.getModelContext().getQuantAcount().getStartDate();
		Date endDate = DateUtil.getLast(context.getCurrentDate());
		List<TradeBill> bills = context.getTradeBillService().getBills(acountId, startDate , endDate);
		
		TradeBill lastBill= null;
		for (int i = bills.size() - 1; i >= 0; i--) {
			TradeBill bill = bills.get(i);
			boolean isSellBuy = TradeType.get(bill).isSellBuy();
			if(isSellBuy && lastBill!=null){
				boolean isSameDay = DateUtil.isSameDay(bill.getBillTime(), lastBill.getBillTime());
				boolean isSameSec = bill.getSecId().equals(lastBill.getSecId());
				if(isSameDay&& !isSameSec){
					return bill.getBillTime();
				} else {
					lastBill = bill;
				}
			} else {
				lastBill = bill;
			}
		}
		return startDate;
	}

}
